package com.myzhouye.biz.security;/***
 *                    _ooOoo_
 *                   o8888888o
 *                   88" . "88
 *                   (| -_- |)
 *                    O\ = /O
 *                ____/`---'\____
 *              .   ' \\| |// `.
 *               / \\||| : |||// \
 *             / _||||| -:- |||||- \
 *               | | \\\ - /// | |
 *             | \_| ''\---/'' | |
 *              \ .-\__ `-` ___/-. /
 *           ___`. .' /--.--\ `. . __
 *        ."" '< `.___\_<|>_/___.' >'"".
 *       | | : `- \`.;`\ _ /`;.`/ - ` : | |
 *         \ \ `-. \_ __\ /__ _/ .-` / /
 * ======`-.____`-.___\_____/___.-`____.-'======
 *                    `=---='
 *
 * .............................................
 *          佛祖保佑             永无BUG
 */

/**
 * @program: game01
 * @description:
 * @author: 那条蠢鱼
 * @create: 2025-11-05 14:22
 **/

import cn.hutool.core.util.IdUtil;
import com.myzhouye.biz.bean.LoginUser;
import com.myzhouye.biz.model.User;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.Collection;

@Component
@RequiredArgsConstructor
public class RedisTokenUtils {

    private final ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;
    private static final String TOKEN_PREFIX = "zy01:login:"; // 统一前缀：区分其他Redis键
    private static final Duration TOKEN_EXPIRE = Duration.ofHours(2);  // Token有效期2小时

    /**
     * 生成Token并存储LoginUser到Redis
     * @param user 原始用户实体（从数据库查询）
     * @param authorities 用户权限（从Security上下文获取）
     * @return 生成的UUID Token
     */
    public Mono<String> generateToken(User user, Collection<? extends GrantedAuthority> authorities) {
        // 1. 生成UUID作为Token
        String token = IdUtil.fastUUID();
        String redisKey = TOKEN_PREFIX + token;

        // 2. 封装LoginUser（仅包含登录必要信息，避免存储冗余字段）
        LoginUser loginUser = new LoginUser(
                user.getId(),
                user.getUsername(),
                user.getNickname(),
                authorities,
                null
        );

        // 3. 存储到Redis（键：token，值：LoginUser对象，设置过期时间）
        return reactiveRedisTemplate.opsForValue()
                .set(redisKey, loginUser, TOKEN_EXPIRE)
                .thenReturn(token); // 存储成功后返回Token
    }

    /**
     * 从Redis获取LoginUser
     * @param token 前端传入的Token
     * @return 封装好的LoginUser（不存在时返回空Mono）
     */
    public Mono<LoginUser> getLoginUser(String token) {
        String redisKey = TOKEN_PREFIX + token;
        return reactiveRedisTemplate.opsForValue()
                .get(redisKey)
                .cast(LoginUser.class); // 自动反序列化为LoginUser
    }

    /**
     * 刷新Token有效期
     * @param token 前端传入的Token
     * @return 是否刷新成功（true=成功，false=Token不存在）
     */
    public Mono<Boolean> refreshToken(String token) {
        String redisKey = TOKEN_PREFIX + token;
        return reactiveRedisTemplate.expire(redisKey, TOKEN_EXPIRE)
                .defaultIfEmpty(false); // Token不存在时返回false
    }

    /**
     * 注销Token（从Redis删除）
     * @param token 前端传入的Token
     * @return 是否删除成功（true=成功删除，false=Token不存在）
     */
    public Mono<Boolean> invalidateToken(String token) {
        String redisKey = TOKEN_PREFIX + token;
        // 将删除数量（Long）转换为布尔值：>0表示删除成功，否则失败
        return reactiveRedisTemplate.delete(redisKey)
                .map(deletedCount -> deletedCount > 0) // 关键修复：Long -> Boolean
                .defaultIfEmpty(false); // 处理空结果（如Redis连接异常）
    }

    /**
     * 添加：根据用户ID查询已存在的token
     */
    public Mono<String> findExistingTokenByUserId(Long userId) {
        return reactiveRedisTemplate.keys(TOKEN_PREFIX + "*")
                .flatMap(key -> reactiveRedisTemplate.opsForValue().get(key)
                        .cast(LoginUser.class)
                        .filter(loginUser -> loginUser.getUserId().equals(userId))
                        .map(loginUser -> key.substring(TOKEN_PREFIX.length())))
                .next(); // 返回找到的第一个token
    }
}
